using UnityEngine;
using UnityEngine.Rendering;
using System;
using System.Runtime.InteropServices;

class FFInstanceGL : IFFInstance
{
    [StructLayout(LayoutKind.Sequential)]
    public struct FFUnityFrameDataGL
    {
        public FFStructureType sType; // FF_STRUCTURE_TYPE_PER_FRAME_DATA
        public IntPtr pNext;
        public uint flags;
        public uint sceneColor;
        public uint depthStencil;
        public FFMat4x4 viewProj;
        public FFMat4x4 invViewProj;
    };

    [DllImport("frameflowPlugin")]
    private static extern uint FrameFlow_IsActive();

    [DllImport("frameflowPlugin")]
    private static extern int FrameFlow_Activate();

    [DllImport("frameflowPlugin")]
    private static extern int FrameFlow_Deactivate();

    [DllImport("frameflowPlugin")]
    private static extern int FrameFlow_SetPredictionAlgorithm(int algorithm, bool extrapolation);

    [DllImport("frameflowPlugin")]
    private static extern int FrameFlow_SetResolution(int width, int height);

    [DllImport("frameflowPlugin")]
    private static extern int FrameFlow_SetCvvZSemantic(int semantic);

    [DllImport("frameflowPlugin")]
    private static extern IntPtr FrameFlow_GetRenderEventFunc();

    /////////////////////////////////////////////////////////////////
    /// Interface

    string TAG = "FrameFlow GL: ";

    IntPtr m_render_callback = IntPtr.Zero;
    FFUnityFrameDataGL m_frame_data;

    public bool Init(int width, int height, FFPredictionAlgorithm algorithm, bool extrapolation)
    {
        int res = 0;

        Debug.Log(TAG + "Init.");

        int algo = (int)algorithm;
        res = FrameFlow_SetPredictionAlgorithm(algo, extrapolation);
        Debug.Log(TAG + "Set ff algorithm " + algo + ". res = " + res);
        if (res != 0) return false;

        res = FrameFlow_SetResolution(width, height);
        Debug.Log(TAG + "Set ff resolution: " + width + "x" + height + " res =" + res);
        if (res != 0) return false;

        int z_semantic = (int)FFCvvZSemantic.FF_CVV_Z_SEMANTIC_MINUS_ONE_TO_ONE_FORWARD_Z;
        res = FrameFlow_SetCvvZSemantic(z_semantic);
        Debug.Log(TAG + "Set z semantic " + z_semantic + " res = " + res);
        if (res != 0) return false;

        m_render_callback = FrameFlow_GetRenderEventFunc();

        m_frame_data.sType = FFStructureType.FF_STRUCTURE_TYPE_PER_FRAME_DATA;
        m_frame_data.pNext = IntPtr.Zero;
        m_frame_data.flags = (uint)(FFPerFrameDataFlagBits.FF_PER_FRAME_DATA_SCENE_COLOR_BIT
                            | FFPerFrameDataFlagBits.FF_PER_FRAME_DATA_DEPTH_BIT
                            | FFPerFrameDataFlagBits.FF_PER_FRAME_DATA_VIEW_PROJ_BIT
                            | FFPerFrameDataFlagBits.FF_PER_FRAME_DATA_INV_VIEW_PROJ_BIT);
        m_frame_data.sceneColor = 0;
        m_frame_data.depthStencil = 0;

        return true;
    }

    public int SetStencilRemap(FFStencilValueSemantic[] remap)
    {
        // TODO
        return 0;
    }

    public bool Activate()
    {
        Debug.Log(TAG + "Activate start");
        var cmd_buffer_provide = CommandBufferPool.Get();
        cmd_buffer_provide.IssuePluginEventAndData(m_render_callback, (int)FFRenderEventId.FRAME_FLOW_ACTIVATE_EVENT_ID, (IntPtr)0);
        CMDExecute(cmd_buffer_provide);
        return true;
    }

    public bool Deactivate()
    {
        int res = FrameFlow_Deactivate();
        Debug.Log(TAG + "Deactivate: (int)res=" + res);
        return (res == 0);
    }

    public bool IsActive()
    {
        uint res = FrameFlow_IsActive();  // return FF_FALSE(0),FF_TRUE(1)
        Debug.Log(TAG + "IsActive: (bool)res=" + res);
        return (res != 0);
    }

    public void Feed(Matrix4x4 viewProj, IntPtr color, IntPtr depth)
    {
        m_frame_data.viewProj = ConvertMatrix(viewProj);
        m_frame_data.invViewProj = ConvertMatrix(viewProj.inverse);
        m_frame_data.sceneColor = (uint)color;
        m_frame_data.depthStencil = (uint)depth;

        GCHandle frame_data = GCHandle.Alloc(m_frame_data, GCHandleType.Pinned);

        var cmd_buffer_provide = CommandBufferPool.Get();
        IntPtr frame_data_ptr = frame_data.AddrOfPinnedObject();
        cmd_buffer_provide.IssuePluginEventAndData(m_render_callback, (int)FFRenderEventId.FRAME_FLOW_PROVIDE_DATA_EVENT_ID, frame_data_ptr);
        CMDExecute(cmd_buffer_provide);

        frame_data.Free();
    }

    public void Predict(IntPtr predict)
    {
        var cmd_buffer_predict = CommandBufferPool.Get("Predict");
        cmd_buffer_predict.IssuePluginEventAndData(m_render_callback, (int)FFRenderEventId.FRAME_FLOW_PREDICT_EVENT_ID, predict);
        CMDExecute(cmd_buffer_predict);
    }

    void CMDExecute(CommandBuffer commandBuffer)
    {
        Graphics.ExecuteCommandBuffer(commandBuffer);
        commandBuffer.Clear();
        CommandBufferPool.Release(commandBuffer);
    }

    unsafe FFMat4x4 ConvertMatrix(Matrix4x4 mx)
    {
        // ff takes column-major matrices as input
        FFMat4x4 ff_mx;
        for (int i = 0; i < 4; ++i)
        {
            Vector4 row = mx.GetColumn(i);
            for (int j = 0; j < 4; ++j)
            {
                ff_mx.data[i * 4 + j] = row[j];
            }
        }
        return ff_mx;
    }

    [StructLayout(LayoutKind.Sequential)]
    unsafe public struct FFStencilRemapInfo
    {
        public fixed int remapTable[256];
    };

    [StructLayout(LayoutKind.Sequential)]
    unsafe public struct FFMat4x4
    {
        public fixed float data[16];
    };
};